#include "cmdline.h"
#include "getopt.h"
#include <string.h>
#include "usart.h"
#include <ctype.h>
#include <stdlib.h>

#define USE_FREERTOS

#ifdef USE_FREERTOS
    #include "FreeRTOS.h"
    #include "queue.h"
    extern QueueHandle_t UartQueueHandle;
#endif

#define CMDINE_HOST     "Chen@STM32F4: "
#define CMDLINE_VERSION "1.1"
#define cmdline_printf(fmt, ...)  printf(fmt, ##__VA_ARGS__)  
#define CMD_ARG_COUNT_MAX 10 //最多10个参数

typedef struct {
    int argc; // 参数个数
    char **argv; // 参数列表
} Args;

static Cmd cmdline_head_node = {
    .cb = NULL,
    .next = NULL,
    .cmd = NULL,
};

static Args* parseArguments(char *command) {
    Args *result = malloc(sizeof(Args));
    result->argc = 0;
    const char *delim = " "; // 分隔符为空格
    
    // 动态分配内存给参数列表
    result->argv = malloc(CMD_ARG_COUNT_MAX * sizeof(char*)); // 假设最多10个参数
    if (result->argv == NULL) {
        cmdline_printf("Memory allocation failed.\n");
    }
    
    // 使用strtok分割字符串
    char *token = strtok(command, delim);
    while (token != NULL) {
        // 再次动态分配内存给每个参数
        result->argv[result->argc] = strdup(token);
        if (result->argv[result->argc] == NULL) {
            cmdline_printf("Memory allocation failed.\n");
        }
        result->argc++;
        token = strtok(NULL, delim);
    }
    return result;
}

// 释放Args结构体中分配的内存
static void freeArguments(Args *args) {
    for (int i = 0; i < args->argc; i++) {
        free(args->argv[i]);
    }
    free(args->argv);
    free(args);
}

#define IGNORE_CASE 1  // 1 = 忽略大小写, 0 = 区分大小写

// 判断是否匹配前缀（支持大小写可选忽略）
static int starts_with(const char *str, const char *prefix) {
    if (IGNORE_CASE) {
        while (*prefix && *str) {
            if (tolower((unsigned char)*str) != tolower((unsigned char)*prefix))
                return 0;
            str++;
            prefix++;
        }
    } else {
        while (*prefix && *str) {
            if (*str != *prefix)
                return 0;
            str++;
            prefix++;
        }
    }
    return *prefix == '\0';
}

static void completion(const char *buf, linenoiseCompletions *lc) {
    Cmd *i = cmdline_head_node.next; // 跳过头节点
    char common_prefix[128] = {0};

    while (i != NULL) {
        if (starts_with(i->cmd, buf)) {
            linenoiseAddCompletion(lc, i->cmd);
        }
        i = i->next;
    }
}

static char *hints(const char *buf, int *color, int *bold) {
    if (!strcasecmp(buf,"hello")) {
        *color = 35;
        *bold = 0;
        return " World";
    }
    return NULL;
}

int cmdline_new(const char *cmd, cmdline_cb cb)
{
    Cmd *cmd_new = (Cmd*)malloc(sizeof(Cmd));
    cmd_new->cmd = strdup(cmd);
    cmd_new->cb = cb;
    cmd_new->next = NULL;
    Cmd *i = &cmdline_head_node;
    while (i->next != NULL) {
        i = i->next;
    }
    i->next = cmd_new;
    return 0;
}

static Cmd* cmdline_find(const char *cmd)
{
    Cmd *i = &cmdline_head_node;
    while (i->next != NULL) {
        if (strcmp(i->next->cmd, cmd) == 0) {
            return i->next;
        }
        i = i->next;
    }
    return NULL;
}

int cmdline_del(const char *cmd)
{
    Cmd *i = cmdline_find(cmd);
    if (i == NULL) {
        cmdline_printf("Cmd not found!!!\r\n");
        return -1;
    } else {
        free(i->cmd);
        free(i);
    }
    return 0;
}

static void cmdline(char *line_in)
{
    if (strlen(line_in) == 0) { // 命令行仅输入回车
        return;
    }
    char *line = strdup(line_in);
    Args *args = parseArguments(line);
    // if ( (args->argc > 1) && (args->argv[1][0] != '-')){ //arm-none-eabi-gcc getopt does not meet the standards
    //     cmdline_printf("cmd err\r\n");
    //     goto err;
    // }    
    Cmd *cmd = cmdline_find(args->argv[0]);
    if (cmd == NULL) {
        cmdline_printf("%s: Cmd not found!\r\n", args->argv[0]);
        goto err;
    } else {
        optind = 1;
        cmd->cb(args->argc, args->argv);
    }
    // 释放内存
err:    
    freeArguments(args);
    free(line);
}

static int cmdline_list(int argc, char **argv)
{
    Cmd *i = &cmdline_head_node;
    while (i->next != NULL) {
        cmdline_printf(" * %s\n", i->next->cmd);
        i = i->next;
    }
    return 0;
}

static int cmdline_test(int argc, char **argv)
{
    for (int i = 0; i < argc; i++) {
        cmdline_printf(" * %d --> %s\r\n", i, argv[i]);
    }
    int c;
    while ((c = getopt(argc, argv, "abc:")) != -1) {
        switch (c) {
        case 'a':
            cmdline_printf("a \r\n");
            break;
        case 'b':
            cmdline_printf("b \r\n");
            break;
        case 'c':
            cmdline_printf("c : %s\r\n", optarg);
            break;
        default:
            break;
        }
    }
    cmdline_printf("optind: %d\r\n", optind);
    return 0;
}

int cmdine_start(void)
{
    char *line;
    linenoiseSetCompletionCallback(completion);
    // linenoiseSetHintsCallback(hints);
    linenoiseClearScreen();
    cmdline_printf(
        "Welcome to cmdine "CMDLINE_VERSION" (STM32 ARM CORTEX-M4 ARCH-V%d)"
        "\r\n\r\n"
        " * Use cmdline_list to list all commands"
        "\r\n\r\n"
        " * Use the tab completion command"
        "\r\n\r\n"
        " * Use the up and down arrows to browse the history command"
        "\r\n\r\n"
        "Firmware compile time: %s %s\r\n",__ARM_ARCH, __DATE__, __TIME__
    );
    cmdline_new("cmdlist", cmdline_list);
    cmdline_new("cmdline_test", cmdline_test);
    while (1){
        line = linenoise(CMDINE_HOST);
        if (line == NULL) {
            cmdline_printf("\r\nlinenoise err!!!");
            break;
        }
        if (!strncmp(line,"historylen",10)) {
            /* The "/historylen" command will change the history len. */
            int len = atoi(line+10);
            linenoiseHistorySetMaxLen(len);
        } 
        linenoiseHistoryAdd(line);        
        cmdline(line);
        free(line);
    }
    return -1;
}


int cmdline_get_ctrl_c(void) 
{
     uint8_t data = 0;
#ifdef USE_FREERTOS
    xQueueReceive(UartQueueHandle, &data, 5/portTICK_PERIOD_MS);
#else
    if (Read_RingBuff(&Uart1_RingBuff, &data) != RINGBUFF_OK) {
        return -1;
    }
#endif
    if (data != 0x03) {
        return -1;
    }
    return 0;
}